<?php
namespace WDB\Query;
use WDB;

/**
 * Query logging class used by WDB\Database.
 *
 * @author Richard Ejem <richard(at)ejem.cz>
 * @package WDB
 */
final class Log extends WDB\BaseObject
{
    /**log all database queries (creates really large logs because selects are usually called far more often than write queries*/
    const LOG_ALL = 3; 
    
    /**log only write (insert, delete, update) or erroneous queries. Recommended
     * for standard development or if you need to keep track of who changed what
     * in the application.
     * @see WDB\Query\Log::$id_user
     */
    const LOG_WRITE = 2;
    
    /**log only erroneous queries which cause a database error.*/
    const LOG_ERRONEOUS = 1;
    
    /**do not log anything*/
    const LOG_NOTHING = 0;
    
    
    /**@var WDB\Database*/
    private $logDB = NULL;
    
    /**
     * Defines user ID for current session. This is not automatically configured by framework.
     * 
     * @var int|NULL*/
    private $idUser = NULL;
    
    /**
     * Enumeration Log::LOG_*
     *
     * @var int
     */
    private $level;
    
    /**
     * Unique value generated once per PHP script execution.
     * 
     * Written to log with each query to help determine which queries were ran during one script execution.
     *
     * @var string
     */
    private static $requestID = NULL;
    
    /**
     * Default value for $idUser for each instance.
     *
     * @var int|NULL
     */
    public static $id_user = NULL;
    
    /**
     * Default value for $level for each instance.
     * 
     * Enumeration Log::LOG_*
     *
     * @var int
     */
    public static $default_level = self::LOG_ERRONEOUS;
    
    /**
     * Creates new LOG with default level and idUser.
     */
    public function __construct()
    {
        $this->level = self::$default_level;
        $this->idUser = self::$id_user;
    }
    
    /**
     * get user ID for current instance.
     *
     * @return int|NULL
     */
    public function getIdUser()
    {
        return $this->idUser;
    }
    
    /**
     * set user id for current instance. Application can supply any integer to Log class as "user ID", which will be
     * saved with all logged queries to easily trace which application user did an action that invoked the query.
     * User logic is NOT maintained by WDB, only this ID is supplied from application.
     *
     * @param int|NULL user ID
     */
    public function setIdUser($value)
    {
        $this->idUser = ($value === NULL ? NULL : intval($value));
    }
    
    /**
     * Gets log level. Enumeration of Log::LOG_*
     * 
     * LOG_ALL = Log each query. Useful for debugging, not production - may significantly reduce performance/page loading time
     * LOG_WRITE = Log only write queries. Acceptable for production of highly-dependable systems or occasional change tracing.
     * LOG_ERRONEOUS = Log only queries that result into an error. Recommended production value, no erroneous query
     *      should happen on production.
     * LOG_NOTHING = Do not log anything.
     *
     * @return int
     */
    public function getLevel()
    {
        return $this->level;
    }
    
    /**
     * Set log level.
     *
     * @param int log level, enumeration of Log::LOG_*
     */
    public function setLevel($value)
    {
        $this->level = max(self::LOG_NOTHING, min(self::LOG_ALL, $value));
    }
    
    /**
     * Log successful query.
     *
     * @param iQueryResult query result
     */
    public function log(iQueryResult $q)
    {
        if ($this->level < self::LOG_WRITE || !$this->connect()) return;
        if ($q->query instanceof Select)
        {
            if ($this->level < self::LOG_ALL) return;
        }
        elseif ($q->query instanceof Insert)
        {
            if ($this->level < self::LOG_WRITE) return;
        }
        elseif ($q->query instanceof Update)
        {
            if ($this->level < self::LOG_WRITE) return;
        }
        elseif ($q->query instanceof Delete)
        {
            if ($this->level < self::LOG_WRITE) return;
        }
        else
        {
            if ($this->level < self::LOG_ALL) return;
        }
        $this->logQuery($q->query, true);
    }
    
    /**
     * Logs query that's running threw an exception.
     *
     * @param WDB\Query\Query source query
     * @param WDB\Exception\QueryError exception thrown during execution
     */
    public function logError(WDB\Query\Query $query, WDB\Exception\QueryError $ex)
    {
        if ($this->level < self::LOG_ERRONEOUS || !$this->connect()) return;
        $log = $this->logQuery($query, false);
        
        $data = array_merge($ex->logInfo(), array(
            'id_query'=>$log->insertId,
        ));
        
        $query = new Insert('query_errors', $data);
        $query->run($this->logDB);
    }
    
    /**
     * Inserts basic record to a querylog table.
     *
     * @param Query
     * @param bool
     */
    private function logQuery(Query $query, $success)
    {
        if ($query instanceof Select)
        {
            $type = 'select';
        }
        elseif ($query instanceof Insert)
        {
            $type = 'insert';
        }
        elseif ($query instanceof Update)
        {
            $type = 'update';
        }
        elseif ($query instanceof Delete)
        {
            $type = 'delete';
        }
        else
        {
            $type = 'other';
        }
        $sid = session_id();
        if ($sid == '') $sid = NULL;
        $query = new Insert('query_log', $q=array(
            'id_user'=>$this->idUser,
            'session'=>$sid,
            'request'=>self::requestID(),
            'query'=>$query->__toString(),
            'type'=>$type,
            'success'=>$success ? 1 : 0,
        ));
        return $query->run($this->logDB);
    }
    
    /**
     * generate/get one unique ID per php script execution.
     *
     * @return string
     */
    private static function requestID()
    {
        if (self::$requestID === NULL)
        {
            self::$requestID = sha1(uniqid(NULL, TRUE).$_SERVER['REMOTE_ADDR'].$_SERVER['REMOTE_PORT'].$_SERVER['HTTP_USER_AGENT']);
        }
        return self::$requestID;
    }
    
    /**
     * Tries to connect to log database.
     *
     * @return bool true if database is successfully connected.
     */
    private function connect()
    {
        if ($this->logDB === NULL)
        {
            try
            {
                $this->logDB = WDB\Database::getInstance('log');
                $this->logDB->log->level = self::LOG_NOTHING;
            }
            catch (WDB\Exception\ConfigInsufficient $ex)
            {
                $this->logDB = FALSE;
                return FALSE;
            }
        }
        elseif ($this->logDB === FALSE)
        {
            return FALSE;
        }
        return TRUE;
    }
}
